﻿
using System;
using System.Collections.Generic;

namespace Framework.Core
{
    public static class ReferencePool
    {
        private static readonly Dictionary<string, ReferenceCollection> s_ReferenceCollections = new Dictionary<string, ReferenceCollection>();
        public static int Count { get { return s_ReferenceCollections.Count; } }

        public static IReference Get(Type referenceType)
        {
            InternalCheckReferenceType(referenceType);
            return GetReferenceCollection(referenceType).Get();
        }
        public static T Get<T>() where T : class, IReference, new()
        {
            return GetReferenceCollection(typeof(T)).Get<T>();
        }
        public static void Release(IReference reference)
        {
            if (reference == null)
            {
                throw new Exception("Reference is invalid.");
            }
            Type referenceType = reference.GetType();
            //InternalCheckReferenceType(referenceType);
            GetReferenceCollection(referenceType).Release(reference);
        }

        private static void InternalCheckReferenceType(Type referenceType)
        {
            if (referenceType == null)
            {
                throw new Exception("Reference type is invalid.");
            }
            if (!referenceType.IsClass || referenceType.IsAbstract)
            {
                throw new Exception("Reference type is not a non-abstract class type.");
            }
            if (!typeof(IReference).IsAssignableFrom(referenceType))
            {
                throw new Exception(string.Format("Reference type '{0}' is invalid.", referenceType.FullName));
            }
        }

        private static ReferenceCollection GetReferenceCollection(Type referenceType)
        {
            if (referenceType == null)
            {
                throw new Exception("ReferenceType is invalid.");
            }
            string fullName = referenceType.FullName;
            ReferenceCollection referenceCollection = null;
            lock (s_ReferenceCollections)
            {
                if (!s_ReferenceCollections.TryGetValue(fullName, out referenceCollection))
                {
                    referenceCollection = new ReferenceCollection(referenceType);
                    s_ReferenceCollections.Add(fullName, referenceCollection);
                }
            }

            return referenceCollection;
        }

    }

    internal sealed class ReferenceCollection
    {
        private readonly Queue<IReference> m_References;
        private readonly Type m_ReferenceType;
        public EditPoolInfo Info { get; private set; }

        public ReferenceCollection(Type referenceType)
        {
            m_References = new Queue<IReference>(16);
            m_ReferenceType = referenceType;
            Info = new EditPoolInfo(referenceType);
        }

        public Type ReferenceType
        {
            get
            {
                return m_ReferenceType;
            }
        }
        public T Get<T>() where T : class, IReference, new()
        {
            if (typeof(T) != m_ReferenceType)
            {
                throw new Exception("Type is invalid.");
            }
            Info.Pop();
            lock (m_References)
            {
                if (m_References.Count > 0)
                {
                    var ins = (T)m_References.Dequeue();
                    ins.IsReferenceActive = true;
                    return ins;
                }
            }
            var nins = new T();
            nins.IsReferenceActive = true;
            return nins;
        }

        public IReference Get()
        {
            lock (m_References)
            {
                if (m_References.Count > 0)
                {

                    Info.Pop();
                    var ins = m_References.Dequeue();
                    ins.IsReferenceActive = true;
                    return ins;
                }
            }
            var nins = (IReference)Activator.CreateInstance(m_ReferenceType);
            nins.IsReferenceActive = true;
            return nins;
        }

        public void Release(IReference reference)
        {
            if (!reference.IsReferenceActive)
            {
                return;
            }
            reference.Clear();
            reference.IsReferenceActive = false;
            lock (m_References)
            {
                m_References.Enqueue(reference);
            }
            Info.Pop(-1);
            Info.Push();
        }

    }
}
